/* ======================================================================================== *\

		FILE:				PipeLayout.cpp
		AUTHOR:				Jack Hoxley

		DESCRIPTION:		A layout represents the graph of possible moves by any given
							pipe. Due to there being multiple pipes at any one time it is
							necessary to have a central manager/layout that ensures they
							don't overlap or otherwise screw each other over.

		CHANGE:				5th May 2007
								Basic public interface stubs created.
							6th May 2007
								Filled in memory (de)allocation routines.
							14th July 2007
								Implemented iteration counting
								Set up constants to limit the max dimensions

\* ======================================================================================== */

// Include Standard Headers
// ------------------------
#include "DXUT.h"

#pragma warning( disable : 4995 ) // Not interested in deprectation warnings
#include <sstream>
#pragma warning( default : 4995 )

// Include project headers
// -----------------------
#include "Profiling.h"
#include "Utils.h"
#include "PipeLayout.h"



namespace PipeSim
{
	// =================================
	// P R I V A T E   C O N S T A N T S
	// =================================

	const UINT16 DEFAULT_NUMBER_OF_PIPES	= 3;

	const UINT16 DEFAULT_X_SIZE				= 10;
	const UINT16 MAXIMUM_X_SIZE				= 100;

	const UINT16 DEFAULT_Y_SIZE				= 10;
	const UINT16 MAXIMUM_Y_SIZE				= 100;

	const UINT16 DEFAULT_Z_SIZE				= 10;
	const UINT16 MAXIMUM_Z_SIZE				= 100;

	// ===========================
	// P U B L I C   M E M B E R S
	// ===========================
	
	PipeLayout::PipeLayout( )
	{
		ENTER;

		m_SimulationReady	= false;
		m_ppPipes			= NULL;
		m_pppState			= NULL;
		m_IterationCount	= 0;

		CreateLayout( DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE, DEFAULT_NUMBER_OF_PIPES );
	}



	PipeLayout::PipeLayout( const UINT16& nPipes )
	{
		ENTER;

		m_SimulationReady	= false;
		m_ppPipes			= NULL;
		m_pppState			= NULL;
		m_IterationCount	= 0;

		CreateLayout( DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE, nPipes );
	}



	PipeLayout::PipeLayout( const UINT16& xSize, const UINT16& ySize, const UINT16& zSize, const UINT16& nPipes )
	{
		ENTER;

		m_SimulationReady	= false;
		m_ppPipes			= NULL;
		m_pppState			= NULL;
		m_IterationCount	= 0;

		CreateLayout( xSize, ySize, zSize, nPipes );
	}
	

	
	PipeLayout::~PipeLayout( )
	{
		ENTER;

		// Ensure resources are safely
		// and correctly released

		// Free the pipes
		for( int i = 0; i < m_PipeCount; ++i )
			SAFE_DELETE( m_ppPipes[i] );

		SAFE_DELETE_ARRAY( m_ppPipes );

		// Free the state
		for( int x = 0; x < m_XSize; ++x )
		{
			for( int y = 0; y < m_YSize; ++y )
			{
				SAFE_DELETE_ARRAY( m_pppState[x][y] );
			}

			SAFE_DELETE_ARRAY( m_pppState[x] );
		}

		SAFE_DELETE_ARRAY( m_pppState );
	}



	void PipeLayout::IterateSimulation( )
	{
		ENTER;

		if( !m_SimulationReady ) return;

		int dead_pipes = 0;

		for( int i = 0; i < m_PipeCount; ++i )
		{
			// retrieve the current head
			Utils::IntegerVector current = m_ppPipes[i]->Head();

			// Work out what surrounding cells are free
			// and compile a list
			Utils::CoordinateList possible_moves;

			// There are only 6 possible moves: up/down, left/right, forward/backward
			if( Utils::IsInRange( current.x - 1, 0, m_XSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x - 1 ), static_cast< UINT16 >( current.y ), static_cast< UINT16 >( current.z ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x - 1, current.y, current.z ) );

			if( Utils::IsInRange( current.x + 1, 0, m_XSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x + 1 ), static_cast< UINT16 >( current.y ), static_cast< UINT16 >( current.z ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x + 1, current.y, current.z ) );

			if( Utils::IsInRange( current.y - 1, 0, m_YSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x ), static_cast< UINT16 >( current.y - 1 ), static_cast< UINT16 >( current.z ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x, current.y - 1, current.z ) );

			if( Utils::IsInRange( current.y + 1, 0, m_YSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x ), static_cast< UINT16 >( current.y + 1 ), static_cast< UINT16 >( current.z ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x, current.y + 1, current.z ) );

			if( Utils::IsInRange( current.z - 1, 0, m_ZSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x ), static_cast< UINT16 >( current.y ), static_cast< UINT16 >( current.z - 1 ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x, current.y, current.z - 1 ) );

			if( Utils::IsInRange( current.z + 1, 0, m_ZSize - 1 ) && !CheckState( static_cast< UINT16 >( current.x ), static_cast< UINT16 >( current.y ), static_cast< UINT16 >( current.z + 1 ) ) )
				possible_moves.push_back( Utils::IntegerVector( current.x, current.y, current.z + 1 ) );

			if( possible_moves.empty() )
			{
				// There are no free neighbouring cells
				// which implies the pipe would collide with itself or
				// another pipe regardless of where it goes. Don't allow
				// it to move, consider it dead.
				++dead_pipes;
			}
			else
			{
				// Pass the list to the pipe and let it
				// choose
				UINT16 chosen_cell = m_ppPipes[i]->ChooseNextMove( possible_moves );

				// Mark the chosen cell as occupied
				if( chosen_cell < static_cast< UINT16 >( possible_moves.size() ) )
				{
					// Pipe didn't screw up the data - it is still valid!
					m_pppState
						[ possible_moves.at( chosen_cell ).x ]
						[ possible_moves.at( chosen_cell ).y ]
						[ possible_moves.at( chosen_cell ).z ]
						= true;

					// Tag this pipe as having been updated on this iteration
					m_ppPipes[i]->SetLastUpdated( m_IterationCount );
				}
				else
				{
					// Move wasn't valid, so roll it back...
					m_ppPipes[i]->RollBackLastMove( );
				}
			}
		}

		// Check how many dead pipes we have. If all pipes are
		// dead then we should reset the simulation.
		if( dead_pipes == m_PipeCount )
		{
			INFO_OUT( L"All pipes are dead, simulation is being reset..." );
			ResetSimulation( );
		}

		// Increment the iteration counter so we
		// can keep track of when pipes were changed
		++m_IterationCount;
#ifdef DEBUG
		{
			std::wostringstream woss;
			woss << L"Finished iteration number " << m_IterationCount;
			Utils::OutputMessage( Utils::Information, L"PrintPath", __LINE__, woss.str().c_str() );
		}
#endif
	}



	void PipeLayout::ResetSimulation( )
	{
		ENTER;

		if( !m_SimulationReady )
		{
			// No simulation has been created?! So just
			// create one, which by implication resets the
			// whole thing.
			CreateLayout( DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE, DEFAULT_NUMBER_OF_PIPES );
		}
		else
		{
			// Just reset existing structures...
			for( int x = 0; x < m_XSize; ++x )
			{
				for( int y = 0; y < m_YSize; ++y )
				{
					for( int z = 0; z < m_ZSize; ++z )
					{
						// Reset this node to a 'free' state
						m_pppState[x][y][z] = false;
					}
				}
			}

			// Now recreate the pipes
			CreatePipes( );
		}
	}



	UINT16 PipeLayout::LayoutXDimension( ) const
	{
		ENTER;

		return m_XSize;
	}



	UINT16 PipeLayout::LayoutYDimension( ) const
	{
		ENTER;

		return m_YSize;
	}



	UINT16 PipeLayout::LayoutZDimension( ) const
	{
		ENTER;

		return m_ZSize;
	}



	UINT16 PipeLayout::NumberOfPipes( ) const
	{
		ENTER;

		return m_PipeCount;
	}



	UINT32 PipeLayout::IterationOfLastChange( const UINT16& pipe ) const
	{
		if( pipe >= m_PipeCount ) return 0;

		return m_ppPipes[pipe]->LastUpdated( );
	}



	void PipeLayout::GetPipeCoordinates( const UINT16& pipe, Utils::CoordinateList* pOut ) const
	{
		if( pipe >= m_PipeCount ) return;

		m_ppPipes[pipe]->CopyCurrentCoordinateList( pOut );
	}



	UINT32 PipeLayout::PipeLength( const UINT16& pipe ) const
	{
		if( pipe >= m_PipeCount ) return 0;

		return m_ppPipes[pipe]->GetPipeLength();
	}
	


	// =============================
	// P R I V A T E   M E M B E R S
	// =============================

	void PipeLayout::CreateLayout( const UINT16& xSize, const UINT16& ySize, const UINT16& zSize, const UINT16& nPipes )
	{
		ENTER;

		// Don't overwrite an existing simulation
		if( m_SimulationReady )
			return;

		// Store these attributes for later
		m_PipeCount		= nPipes;
		m_XSize			= (xSize > MAXIMUM_X_SIZE) ? MAXIMUM_X_SIZE : xSize;
		m_YSize			= (ySize > MAXIMUM_Y_SIZE) ? MAXIMUM_Y_SIZE : ySize;
		m_ZSize			= (zSize > MAXIMUM_Z_SIZE) ? MAXIMUM_Z_SIZE : zSize;

#ifdef DEBUG
		std::wostringstream woss;
		woss << L"Creating layout with dimensions of " << xSize << L" x " << ySize << L" x " << zSize;
		Utils::OutputMessage( Utils::Information, L"CreateLayout", __LINE__, woss.str().c_str() );
#endif

		// Allocate the state block memory
		m_pppState = new bool**[ m_XSize ];
		for( int x = 0; x < m_XSize; ++x )
		{
			m_pppState[x] = new bool*[ m_YSize ];

			for( int y = 0; y < m_YSize; ++y )
			{
				m_pppState[x][y] = new bool[ m_ZSize ];

				for( int z = 0; z < m_ZSize; ++z )
				{
					// Reset this node to a 'free' state
					m_pppState[x][y][z] = false;
				}
			}
		}

		CreatePipes( );

		// Flag the simulation as ready to go:
		m_SimulationReady = true;
	}



	void PipeLayout::CreatePipes( )
	{
		// Remove any existing memory
		if( NULL != m_ppPipes )
		{
			for( int i = 0; i < m_PipeCount; ++i )
				SAFE_DELETE( m_ppPipes[i] );

			SAFE_DELETE_ARRAY( m_ppPipes );
		}

		// Create each pipe
		m_ppPipes = new Pipe*[ m_PipeCount ];

		for( int i = 0; i < m_PipeCount; ++i )
		{
			// Determine a new starting point
			Utils::IntegerVector StartPos;
			do
			{
				StartPos.x = static_cast< UINT16 >( Utils::RndInRange( 0, m_XSize ) );
				StartPos.y = static_cast< UINT16 >( Utils::RndInRange( 0, m_YSize ) );
				StartPos.z = static_cast< UINT16 >( Utils::RndInRange( 0, m_ZSize ) );
			}
			while( CheckState( static_cast< UINT16 >( StartPos.x ), static_cast< UINT16 >( StartPos.y ), static_cast< UINT16 >( StartPos.z ) ) );

			// Mark this location as used
			m_pppState[ StartPos.x ][ StartPos.y ][ StartPos.z ] = true;

			// Create the actual pipe
			m_ppPipes[i] = new Pipe( StartPos );
			m_ppPipes[i]->SetLastUpdated( m_IterationCount );

			// DBG
#ifdef DEBUG
			std::wostringstream woss;
			woss << L"New pipe created @ X = " << StartPos.x << L", Y = " << StartPos.y << L", Z = " << StartPos.z;
			Utils::OutputMessage( Utils::Information, L"CreateLayout", __LINE__, woss.str().c_str() );
#endif
		}
	}



	bool PipeLayout::CheckState( const UINT16& x, const UINT16& y, const UINT16& z )
	{
		ENTER;

		if( !m_SimulationReady ) return false;

		if( (x >= m_XSize) || (y >= m_YSize) || (z >= m_ZSize) )
			return false; /* should throw error?! */

		return m_pppState[x][y][z];
	}
};